////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNScripting/inc/WNScriptingEngine.h"
#include "WNScripting/inc/WNScriptingEngineInternal.h"
#include "WNScripting/inc/WNScriptLexerDefines.h"
#include "WNScripting/inc/WNParseTree.h"
#include "WNScripting/inc/WNFunctionManager.h"
#include "WNScripting/inc/WNLLVMIncludes.h"
#include "WNScripting/inc/WNScriptDefinedElement.h"
#include "WNScripting/inc/WNInteropSimpleIO.h"
#include "WNCore/inc/WNAssert.h"
#include "WNFileIO/inc/WNFileIO.h"
#include "WNMemory/inc/WNMemory.h"
#include "WNStrings/inc/WNStrings.h"
#include "WNConcurrency/inc/WNCriticalSection.h"

using namespace WNFileIO;
using namespace WNStrings;
using namespace WNMemory;

WN_SIZE_T WNGetScriptingEngineSize() {
    return(sizeof(WNScriptingEngineImpl));
}

WNScriptingEngine* WNCreateScriptingEngine(WN_VOID* _location) {
    WNScriptingEngineImpl* engine;
    if(_location != WN_NULL) {
        engine = WN_PLACEMENT_NEW(WNScriptingEngineImpl(), _location);
    } else {
        engine =  WN_NEW(WNScriptingEngineImpl());
    }
    engine->SetIsHeap(engine == WN_NULL);
    return(engine);
}

WN_VOID WNDestroyScriptingEngine(WNScriptingEngine* _engine) {
    WNScriptingEngineImpl* impl = reinterpret_cast<WNScriptingEngineImpl*>(_engine);
    if(impl->GetIsHeap()) {
        WN_DELETE(impl);
    } else {
        impl->~WNScriptingEngineImpl();
    }
}

WNScriptingEngineImpl::~WNScriptingEngineImpl() {
	mCheckedFiles.clear();
}

WN_VOID WNScriptingEngineImpl::AddThread()
{
    WN_RELEASE_ASSERT_DESC(mScriptingEngineData.mScriptingEngine, "Error, can not add a thread to an uninitialized scripting engine");
    mScriptingEngineData.mFunctionManager->AddNewThread();
}

WN_BOOL WNScriptingEngineImpl::Initialize() {
    std::string Error;
    llvm::InitializeNativeTarget();

    mScriptingEngineData.mScriptingEngine = this;
    mScriptingEngineData.mIdentifierMap = WN_NEW(WNIdentifierMap());
    mScriptingEngineData.mScopedVariableList = WN_NEW(WNScopedVariableList());
    mScriptingEngineData.mModule = WN_NEW(llvm::Module("JitModule", llvm::getGlobalContext()));
    mScriptingEngineData.mBuilder = WN_NEW(llvm::IRBuilder<>(llvm::getGlobalContext()));
    llvm::EngineBuilder builder(mScriptingEngineData.mModule);
    builder.setErrorStr(&Error);

#ifdef WN_USE_INTERPRETER
    builder.setEngineKind(llvm::EngineKind::Interpreter);
    builder.setJITMemoryManager(0);
#else
    builder.setEngineKind(llvm::EngineKind::JIT);
    builder.setJITMemoryManager(llvm::JITMemoryManager::CreateDefaultMemManager());
#endif

    #ifdef WN_SCRIPTING_GENERATE_OPTIMIZED
    builder.setOptLevel(llvm::CodeGenOpt::Aggressive);
    #else
    builder.setOptLevel(llvm::CodeGenOpt::None);
    #endif

    mScriptingEngineData.mExecutionEngine = builder.create();
    mScriptingEngineData.mTypeManager = WN_NEW(WNTypeManager(*mScriptingEngineData.mIdentifierMap));
    mScriptingEngineData.mTypeManager->Initialize(mScriptingEngineData);
    mScriptingEngineData.mFunctionManager = WN_NEW(WNFunctionManager(mScriptingEngineData));
    mScriptingEngineData.mFunctionPassManager = WN_NEW(llvm::FunctionPassManager(mScriptingEngineData.mModule));

    #if 0//WN_SCRIPTING_GENERATE_OPTIMIZED
        mScriptingEngineData.mFunctionPassManager->add(llvm::createCFGSimplificationPass());  
        mScriptingEngineData.mFunctionPassManager->add(llvm::createBasicAliasAnalysisPass());
        mScriptingEngineData.mFunctionPassManager->add(llvm::createPromoteMemoryToRegisterPass());
        mScriptingEngineData.mFunctionPassManager->add(llvm::createInstructionCombiningPass());
        mScriptingEngineData.mFunctionPassManager->add(llvm::createReassociatePass());
        mScriptingEngineData.mFunctionPassManager->add(llvm::createGVNPass());
        mScriptingEngineData.mFunctionPassManager->add(llvm::createCFGSimplificationPass());
        mScriptingEngineData.mFunctionPassManager->add(llvm::createDeadCodeEliminationPass());
        mScriptingEngineData.mFunctionPassManager->add(llvm::createDeadStoreEliminationPass());
        mScriptingEngineData.mFunctionPassManager->add(llvm::createDeadInstEliminationPass());
        mScriptingEngineData.mFunctionPassManager->add(llvm::createCFGSimplificationPass());
        
    #endif

    mScriptingEngineData.mFunctionPassManager->doInitialization();

    if (mScriptingEngineData.mExecutionEngine == WN_NULL) {
        WN_SNPRINTF(mErrorString, 511, "%s", Error.c_str());

        return(WN_FALSE);
    }

    WN_RELEASE_ASSERT(mScriptingEngineData.mIdentifierMap &&
                      mScriptingEngineData.mTypeManager   &&
                      mScriptingEngineData.mScopedVariableList &&
                      mScriptingEngineData.mModule &&
                      mScriptingEngineData.mBuilder &&
                      mScriptingEngineData.mExecutionEngine);

    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&getchar), WN_FALSE, "WN_CHAR", "getCh", "");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&putchar), WN_FALSE,  "WN_VOID", "putCh", "WN_CHAR");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&WNInterop::WNWriteString), WN_FALSE, "WN_VOID", "writeString", "Array<WN_CHAR>");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&WNInterop::WNWriteString2), WN_FALSE, "WN_VOID", "writeString", "Array<WN_CHAR>, WN_INT32");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&WNInterop::WNReadLine), WN_FALSE, "WN_INT32", "readLine", "Array<WN_CHAR>");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&WNInterop::WNStackReadLine), WN_TRUE, "Array<WN_CHAR>", "stackReadLine", "WN_INT32");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&WNInterop::WNAppend0), WN_FALSE, "WN_INT32", "appendToBuff", "Array<WN_CHAR>, WN_INT32, WN_INT32");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&WNInterop::WNAppend1), WN_FALSE, "WN_INT32", "appendToBuff", "Array<WN_CHAR>, WN_INT32, WN_CHAR");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&WNInterop::WNAppend2), WN_FALSE, "WN_INT32", "appendToBuff", "Array<WN_CHAR>, WN_INT32, WN_FLOAT32");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&WNInterop::WNAppend3), WN_FALSE, "WN_INT32", "appendToBuff", "Array<WN_CHAR>, WN_INT32, WN_INT32, WN_INT32");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&WNInterop::WNAppend4), WN_FALSE, "WN_INT32", "appendToBuff", "Array<WN_CHAR>, WN_INT32, WN_FLOAT32, WN_INT32, WN_INT32");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&WNInterop::WNAppend5), WN_FALSE, "WN_INT32", "appendToBuff", "Array<WN_CHAR>, WN_INT32, Array<WN_CHAR>");
    mScriptingEngineData.mFunctionManager->AddExternalFunction(reinterpret_cast<WN_VOID*>(&WNInterop::WNAppend6), WN_FALSE, "WN_INT32", "appendToBuff", "Array<WN_CHAR>, WN_INT32, Array<WN_CHAR>, WN_INT32");
    return(WN_TRUE);
}

WN_BOOL WNScriptingEngineImpl::RunFunction(const WN_CHAR* _file, const WN_CHAR* _function, const std::vector<WNParameter>& _params, WNParameter& _outParam) {
    WNIdentifierElement fileName = mScriptingEngineData.mIdentifierMap->GetIdentifier(_file, WNStrLen(_file));
    WNIdentifierElement functionName = mScriptingEngineData.mIdentifierMap->GetIdentifier(_function, WNStrLen(_function));

    if (!QuickParseFile(fileName)) {
        return(WN_FALSE);
    }

    WNFunction* func;
    std::vector<WNTypeElement> types;

    for (size_t i = 0; i < _params.size(); ++i) {
        types.push_back(_params[i].mType);
    }

    WNFunctionManager::ReturnCode retCode = mScriptingEngineData.mFunctionManager->GetExactScriptFunction(func, functionName, types, fileName);

    if (retCode != WNFunctionManager::WN_OK) {
        return(WN_FALSE);
    }

    CompileFunction(func);

    if (func->mFunctionPointer == WN_NULL) {
        return(WN_FALSE);
    }

    std::vector<llvm::GenericValue> values;

    for (size_t i = 0; i < _params.size(); ++i) {
        llvm::GenericValue v;

        v.DoubleVal = _params[i].mDoubleVal;
        v.IntVal = llvm::APInt(64, _params[i].mInt64val, WN_TRUE);
        values.push_back(v);
    }
    
    llvm::GenericValue v = mScriptingEngineData.mExecutionEngine->runFunction(func->mLlvmFunction, values);

    if (v.IntVal == 0) {
        _outParam.mDoubleVal = v.DoubleVal;
    } else {
        _outParam.mInt64val = v.IntVal.getZExtValue();
    }
    
    return(func->mFunctionPointer != WN_NULL);
}

WN_BOOL WNScriptingEngineImpl::QuickParseFile(WNIdentifierElement _mName) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mScriptingEngineLock.Lock();
    #endif

    std::list<WNIdentifierElement> quickLexIdentifiers;
    quickLexIdentifiers.push_back(_mName);
    while(!quickLexIdentifiers.empty()) {
        WNIdentifierElement filename = quickLexIdentifiers.front();
        quickLexIdentifiers.pop_front();

        if (mCheckedFiles.find(filename) != mCheckedFiles.end()) {
            continue;
        }
        mScriptingEngineData.mFunctionManager->AddIncludeFile(filename, filename);
        WN_BOOL retVal = WN_TRUE;
        WNLexerInfo lexer;

        lexer.mIdentifierMap = mScriptingEngineData.mIdentifierMap;
        lexer.mTypeManager = mScriptingEngineData.mTypeManager;
        lexer.mFilename = filename;

        std::list<WNScriptDefinedElement*> mElements;
        WNFile f = WNFile();
        
        if ( f.OpenFile(mScriptingEngineData.mIdentifierMap->GetStringFromIdentifier(filename), WNFile::WN_READ | WNFile::WN_EXCLUSIVE) == WNFile::WN_OK ) {
            lexer.mFileStart = f.GetDataBuffer();
            lexer.mStart = f.GetDataBuffer();
            WNString errorMsg;
            WNQuickLex(&lexer, errorMsg, mElements, mScriptingEngineData);
        }
    
        std::list<WNScriptDefinedElement*>::iterator i;

        for (i = mElements.begin(); i != mElements.end(); ++i) {
            WNScriptDefinedElement* curElem = *i;

            if (curElem->mType == eWNScriptFunction) {
                std::vector<WNFunction*> funcs;
                mScriptingEngineData.mFunctionManager->GetScriptFunctions(funcs, curElem->mGlobalIdentifier, filename);

                for (std::vector<WNFunction*>::iterator fIt = funcs.begin(); fIt != funcs.end(); ++fIt) {
                    WNFunction* func = (*fIt);

                    if (func->mTypes.size() != curElem->mArguments.size()) {
                        continue;
                    }

                    WN_BOOL equals = WN_TRUE;

                    for (WN_SIZE_T k = 0; k < func->mTypes.size(); ++k) {
                        if (func->mTypes[k]->mScriptType != curElem->mArguments[k]) {
                            equals = WN_FALSE;

                            break;
                        }
                    }

                    if (equals) {
                        retVal = WN_FALSE;

                        WN_SNPRINTF(mErrorString, 511, "Error function %s already exists %s:[%d]", 
                                                        mScriptingEngineData.mIdentifierMap->GetStringFromIdentifier(curElem->mGlobalIdentifier), 
                                                        mScriptingEngineData.mIdentifierMap->GetStringFromIdentifier(_mName),
                                                        curElem->mOffset);

                        break;
                    }
                }

                if (retVal != WN_FALSE) {
                    mScriptingEngineData.mFunctionManager->AddIncompleteScriptFunction(curElem->mGlobalIdentifier, curElem->mFileName, curElem->mOnStack, curElem->mReturnType, curElem->mArguments, curElem->mOffset, curElem->mEndOffset);
                }
            } else if (curElem->mType == eWNScriptInclude) {
                const WN_CHAR* name = mScriptingEngineData.mIdentifierMap->GetStringFromIdentifier(curElem->mIncludeName);
                const WN_CHAR* curFilePath = mScriptingEngineData.mIdentifierMap->GetStringFromIdentifier(curElem->mFileName);
                WN_SIZE_T filePath = WNGetPathFromFilename(curFilePath);
                WN_SIZE_T allocSize = filePath + WNStrLen(name) + 1;
                WN_CHAR* newPath = static_cast<WN_CHAR*>(WN_STACK_ALLOC(allocSize));
                WNMemCpy(newPath, curFilePath, filePath);
                newPath[filePath] = '\0';
                
                if(!WNAppendPath(newPath, name, allocSize)) {
                    retVal = WN_FALSE;
                    WN_SNPRINTF(mErrorString, 511, "Cannot find include file %s: %s:[%d]", name, curFilePath, curElem->mOffset);
                } else {
                    WNIdentifierElement elem = mScriptingEngineData.mIdentifierMap->GetIdentifier(newPath, WNStrLen(newPath));
                    quickLexIdentifiers.push_back(elem);
                    mScriptingEngineData.mFunctionManager->AddIncludeFile(filename, elem);
                }   
            }
            WN_DELETE(curElem);
        }

        mCheckedFiles.insert(_mName);
        if(retVal == false){
            #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
                mScriptingEngineLock.Unlock();
            #endif
            return(WN_FALSE);
        }
    }

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mScriptingEngineLock.Unlock();
    #endif

    return(WN_TRUE);
}

void WNScriptingEngineImpl::CompileFunction(WNFunction* _func) {
    if (_func->mFunctionPointer != WN_NULL){
        return;
    }

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mScriptingEngineLock.Lock();
    #endif

    WNLexerInfo lexer;
    lexer.mIdentifierMap = mScriptingEngineData.mIdentifierMap;
    lexer.mTypeManager = mScriptingEngineData.mTypeManager;
    WNScriptToken* tok;
    WNParseTree* tree = WN_NULL;

    void * parser = WNScriptParseAlloc(WNMalloc);
#ifdef WN_SCRIPTING_OUTPUT_PARSE_TRACE
    FILE * F = fopen("D:\\parserout.txt", "w+");
    WNScriptParseTrace(F, "->>");
#endif
    WNLexerInitialize(&lexer);

    WNFile f;
    f.OpenFile( mScriptingEngineData.mIdentifierMap->GetStringFromIdentifier(_func->mFile), WNFile::WN_READ | WNFile::WN_EXCLUSIVE);
    WN_CHAR* buff = f.GetDataBuffer();
    lexer.mStart = buff + _func->mOffset;
    lexer.mFileStart = buff + _func->mOffset;
    lexer.mFilename = _func->mFile;
    tok = WNMallocT<WNScriptToken>();
    WN_INT32 ret;
    while (0 <= (ret = WNScriptLex(&lexer, tok))) {
        if (ret == WN_SCRIPT_ERR) {
            return;
        }

        if (static_cast<WN_SIZE_T>(lexer.mStart - buff) > _func->mEndOffset) {
            WNMemSet(tok, 0x00, sizeof(WNScriptToken));
            break;
        }

        if (ret != WN_SCRIPT_WHITESPACE) {
            WNScriptParse(parser, tok->mId,tok, &tree);
        } 

        tok = WNMallocT<WNScriptToken>();
    }

    WNScriptParse(parser, 0,tok, &tree);

    WNFree(tok);

    #ifdef WN_SCRIPTING_OUTPUT_PARSE_TRACE
        fclose(F);
    #endif

    if(!tree) {
        WNFree(parser);

        #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
            mScriptingEngineLock.Unlock();
        #endif

        WN_DELETE(tree);

        return;
    }

    
    tree->CompileCode(mScriptingEngineData, &_func);

    tree->ReportErrors();
    llvm::verifyFunction(*(_func->mLlvmFunction));


    mScriptingEngineData.mFunctionPassManager->run(*(_func->mLlvmFunction));

    _func->mFunctionPointer = mScriptingEngineData.mExecutionEngine->getPointerToFunction(_func->mLlvmFunction);

#ifdef WN_SCRIPTING_DESTROY_BLOCKS
    llvm::Function::BasicBlockListType::iterator i;
    for(i = _func->mLlvmFunction->getBasicBlockList().begin(); i != _func->mLlvmFunction->getBasicBlockList().end(); ++i)
    {
        i->dropAllReferences();
    }

    while(i = _func->mLlvmFunction->getBasicBlockList().begin(), i != _func->mLlvmFunction->getBasicBlockList().end())
    {
        i->eraseFromParent();
    }
#endif

    WNFree(parser);

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mScriptingEngineLock.Unlock();
    #endif

    WN_DELETE(tree);
}

WN_VOID WNScriptingEngineImpl::DumpModule() {
    if (mScriptingEngineData.mModule) {
        mScriptingEngineData.mModule->dump();
    }
}

WN_BOOL WNScriptingEngineImpl::AddExternalFunction(WN_VOID* _funcPtr, WN_BOOL _stackRet, const WN_CHAR* _returnType, const WN_CHAR* _name, const WN_CHAR* _arglist) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mScriptingEngineLock.Lock();
    #endif

    WN_BOOL success = WN_FALSE;

    if (mScriptingEngineData.mFunctionManager) {
        success = (mScriptingEngineData.mFunctionManager->AddExternalFunction(_funcPtr, _stackRet, _returnType, _name, _arglist) == WNFunctionManager::WN_OK);
    }

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mScriptingEngineLock.Unlock();
    #endif

    return(success);
}

WNScriptingEngineImpl::WNParameter WNScriptingEngineImpl::CreateParameter(WN_CHAR* _type) {
    WNParameter p;
    WNNativeElementType* net = mScriptingEngineData.mTypeManager->GetNativeFromWNType(mScriptingEngineData.mIdentifierMap->GetIdentifier(_type, WNStrLen(_type)));
    if (net) {
        p.mType = net->mScriptType;
    }
    return(p);
}

WN_VOID WNScriptingEngineImpl::SetMallocFunction(WNScriptAllocFunc _scriptFunc) {
    mScriptingEngineData.mFunctionManager->SetMallocFunction(_scriptFunc);
}

WN_VOID WNScriptingEngineImpl::SetFreeFunction(WNScriptFreeFunc _freeFunc) {
    mScriptingEngineData.mFunctionManager->SetFreeFunction(_freeFunc);
}

WN_VOID WNScriptingEngineImpl::SetMemcpyFunction(WNScriptMemcpy _memcpyFunc) {
    mScriptingEngineData.mFunctionManager->SetMemcpyFunction(_memcpyFunc);
}
